package demo.util.http;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 *
 */
public class ApacheHttpClientDemo {


    // 建立连接的超时时间，单位毫秒
    private static final int CONNECTION_TIME_OUT = 1000;
    // 请求超时时间，单位毫秒
    private static final int REQUEST_TIME_OUT = 1000;
    // 最大失败重试次数
    private static final int MAX_FAIL_RETRY_COUNT = 3;
    private static HttpClient httpClient;
    // 请求配置，可以复用
    private static RequestConfig requestConfig;

    static {
        SocketConfig socketConfig = SocketConfig.custom()
                .setSoTimeout(REQUEST_TIME_OUT).setSoKeepAlive(true)
                .setTcpNoDelay(true).build();

        requestConfig = RequestConfig.custom()
                .setSocketTimeout(REQUEST_TIME_OUT)
                .setConnectTimeout(CONNECTION_TIME_OUT).build();
        /**
         * 每个默认的 ClientConnectionPoolManager 实现将给每个route创建不超过2个并发连接，最多20个连接总数。
         */
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
        connManager.setMaxTotal(100);
        // 每个route能使用的最大连接数，一般和MAX_CONNECTION取值一样
        connManager.setDefaultMaxPerRoute(100);
        connManager.setDefaultSocketConfig(socketConfig);

        httpClient = HttpClients.custom()
                .setConnectionManager(connManager)
                // 添加重试处理器
                .setRetryHandler(new MyHttpRequestRetryHandler()).build();
    }

    /**
     * Apache HTTP GET
     *
     * @param url
     * @throws IOException
     */
    public void httpGet(String url) throws IOException {
        try (// 创建HttpClient实例
             CloseableHttpClient httpClient = HttpClientBuilder.create().build();) {
            // 根据URL创建HttpGet实例
            HttpGet httpGet = new HttpGet(url);
            RequestConfig requestConfig = RequestConfig.custom()
                    .setSocketTimeout(2000) // 服务器响应超时时间
                    .setConnectTimeout(2000) // 连接服务器超时时间
                    .setConnectionRequestTimeout(2000)// 设置请求超时时间(单位毫秒)
                    .build();
            httpGet.setConfig(requestConfig);
            // 执行get请求，得到返回体
            HttpResponse response = httpClient.execute(httpGet);
            // 判断是否正常返回
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                // 解析数据
                String data = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
                System.out.println(data);
            }
            // 从响应模型中获取响应实体
            HttpEntity responseEntity = response.getEntity();
            System.out.println("响应状态为:" + response.getStatusLine());
            if (responseEntity != null) {
                System.out.println("响应内容长度为:" + responseEntity.getContentLength());
                System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
            }
        }
    }

    /**
     * HTTP POST FORM
     *
     * @param url
     * @param params
     * @throws IOException
     * @throws ClientProtocolException
     */
    public void httpPostAsForm(String url, Map<String, String> params) throws ClientProtocolException, IOException {
        try (// 创建HttpClient实例
             CloseableHttpClient httpClient = HttpClientBuilder.create().build();) {
            // 根据URL创建HttpPost实例
            HttpPost httpPost = new HttpPost(url);
            RequestConfig requestConfig = RequestConfig.custom()
                    .setSocketTimeout(2000) // 服务器响应超时时间
                    .setConnectTimeout(2000) // 连接服务器超时时间
                    .build();
            httpPost.setConfig(requestConfig);

            List<BasicNameValuePair> pairs = params.entrySet().stream()
                    .map(en -> new BasicNameValuePair(en.getKey(), en.getValue()))
                    .collect(Collectors.toList());
            // 编码格式转换
            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(pairs, "utf-8");
            // 传入请求体
            httpPost.setEntity(entity);
            httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");
            // 发送请求，得到响应体
            CloseableHttpResponse response = httpClient.execute(httpPost);
            System.out.println("响应状态为:" + response.getStatusLine());
            // 从响应模型中获取响应实体
            HttpEntity responseEntity = response.getEntity();
            if (responseEntity != null) {
                System.out.println("响应内容长度为:" + responseEntity.getContentLength());
                System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
            }
            // 判断是否正常返回
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                // 解析数据
                HttpEntity resEntity = response.getEntity();
                String data = EntityUtils.toString(resEntity);
                System.out.println(data);
            }
        }
    }

    /**
     * HTTP POST JSON
     *
     * @param url
     * @param json
     * @throws IOException
     * @throws ParseException
     */
    public void httpPostAsJson(String url, JSONObject json) throws ParseException, IOException {
        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        CloseableHttpResponse response = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            RequestConfig requestConfig = RequestConfig.custom()
                    .setSocketTimeout(2000) // 服务器响应超时时间
                    .setConnectTimeout(2000) // 连接服务器超时时间
                    .build();
            httpPost.setConfig(requestConfig);

            StringEntity entity = new StringEntity(JSON.toJSONString(json), ContentType.APPLICATION_JSON);// 也可以直接使用JSONObject
            httpPost.setEntity(entity);
            // 由客户端执行(发送)请求
            response = httpClient.execute(httpPost);
            System.out.println("响应状态为:" + response.getStatusLine());
            // 从响应模型中获取响应实体
            HttpEntity responseEntity = response.getEntity();
            if (responseEntity != null) {
                System.out.println("响应内容长度为:" + responseEntity.getContentLength());
                System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
            }
        } finally {
            try {
                // 释放资源
                if (httpClient != null) {
                    httpClient.close();
                }
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static class IdleConnectionMonitorThread extends Thread {

        private final HttpClientConnectionManager connMgr;
        private volatile boolean shutdown;

        public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
            super();
            this.connMgr = connMgr;
        }

        @Override
        public void run() {
            try {
                while (!shutdown) {
                    synchronized (this) {
                        wait(5000);
                        // Close expired connections
                        connMgr.closeExpiredConnections();
                        // Optionally, close connections
                        // that have been idle longer than 30 sec
                        connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
                    }
                }
            } catch (InterruptedException ex) {
                // terminate
            }
        }

        public void shutdown() {
            shutdown = true;
            synchronized (this) {
                notifyAll();
            }
        }

    }

    /**
     * 请求重试处理器
     *
     * @author manzhizhen
     */
    private static class MyHttpRequestRetryHandler implements
            HttpRequestRetryHandler {

        @Override
        public boolean retryRequest(IOException exception, int executionCount,
                                    HttpContext context) {
            if (executionCount >= MAX_FAIL_RETRY_COUNT) {
                return false;
            }

            if (exception instanceof InterruptedIOException) {
                // 超时
                return false;
            }
            if (exception instanceof UnknownHostException) {
                // 未知主机
                return false;
            }
            if (exception instanceof ConnectTimeoutException) {
                // 连接被拒绝
                return false;
            }
            if (exception instanceof SSLException) {
                // SSL handshake exception
                return false;
            }

            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();
            boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
            if (idempotent) {
                // 如果请求被认为是幂等的，则重试
                return true;
            }

            return false;
        }
    }


}
